{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bayestree_intro_md"
      },
      "source": [
        "# BayesTree"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "license_cell",
      "metadata": {
        "tags": [
          "remove-cell"
        ]
      },
      "source": [
        "GTSAM Copyright 2010-2022, Georgia Tech Research Corporation,\nAtlanta, Georgia 30332-0415\nAll Rights Reserved\n\nAuthors: Frank Dellaert, et al. (see THANKS for the full author list)\n\nSee LICENSE for the license information"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bayestree_desc_md"
      },
      "source": [
        "A `BayesTree` is a graphical model that represents the result of multifrontal variable elimination on a `FactorGraph`. It is a tree structure where each node is a 'clique' containing a set of conditional distributions $P(\\text{Frontals} | \\text{Separator})$.\n",
        "\n",
        "Each clique k contains a conditional $P(F_k∣S_k)$, where $F_k$ are frontal variables and $S_k$ are separator variables. The joint probability distribution encoded by the Bayes tree is given by the product of all clique conditionals:\n",
        "\n",
        "$$\n",
        "P(X) = \\prod_k P(F_k | S_k)\n",
        "$$\n",
        "\n",
        "Key properties:\n",
        "*   **Cliques:** Each node (clique) groups variables that are eliminated together.\n",
        "*   **Frontal Variables:** Variables eliminated within a specific clique.\n",
        "*   **Separator Variables:** Variables shared between a clique and its parent in the tree. These variables were eliminated higher up in the tree.\n",
        "*   **Tree Structure:** Represents the dependencies introduced during elimination more compactly than a Bayes net, especially for sparse problems.\n",
        "\n",
        "Like `FactorGraph` and `BayesNet`, `BayesTree` is templated on the type of conditional/clique (e.g., `GaussianBayesTree`)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bayestree_colab_md"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/borglab/gtsam/blob/develop/gtsam/inference/doc/BayesTree.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bayestree_pip_code",
        "tags": [
          "remove-cell"
        ]
      },
      "outputs": [],
      "source": [
        "%pip install --quiet gtsam-develop"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "id": "bayestree_import_code"
      },
      "outputs": [],
      "source": [
        "import gtsam\n",
        "import numpy as np\n",
        "import graphviz\n",
        "\n",
        "# We need concrete graph types and elimination to get a BayesTree\n",
        "from gtsam import GaussianFactorGraph, Ordering, GaussianBayesTree, VariableIndex\n",
        "from gtsam import symbol_shorthand\n",
        "\n",
        "X = symbol_shorthand.X\n",
        "L = symbol_shorthand.L"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bayestree_create_md"
      },
      "source": [
        "## Creating a BayesTree (via Elimination)\n",
        "\n",
        "BayesTrees are typically obtained by performing multifrontal elimination on a `FactorGraph`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bayestree_eliminate_code",
        "outputId": "456789ab-cdef-0123-4567-89abcdef0123"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Original Factor Graph:\n"
          ]
        },
        {
          "data": {
            "image/svg+xml": [
              "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
              "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
              " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
              "<!-- Generated by graphviz version 2.50.0 (0)\n",
              " -->\n",
              "<!-- Pages: 1 -->\n",
              "<svg width=\"350pt\" height=\"84pt\"\n",
              " viewBox=\"0.00 0.00 350.00 83.60\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
              "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 79.6)\">\n",
              "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-79.6 346,-79.6 346,4 -4,4\"/>\n",
              "<!-- var7782220156096217089 -->\n",
              "<g id=\"node1\" class=\"node\">\n",
              "<title>var7782220156096217089</title>\n",
              "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-57.6\" rx=\"27\" ry=\"18\"/>\n",
              "<text text-anchor=\"middle\" x=\"99\" y=\"-53.9\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">l1</text>\n",
              "</g>\n",
              "<!-- factor3 -->\n",
              "<g id=\"node9\" class=\"node\">\n",
              "<title>factor3</title>\n",
              "<ellipse fill=\"black\" stroke=\"black\" cx=\"75\" cy=\"-1.8\" rx=\"1.8\" ry=\"1.8\"/>\n",
              "</g>\n",
              "<!-- var7782220156096217089&#45;&#45;factor3 -->\n",
              "<g id=\"edge6\" class=\"edge\">\n",
              "<title>var7782220156096217089&#45;&#45;factor3</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M91.67,-40.17C85.49,-26.32 77.36,-8.1 75.43,-3.76\"/>\n",
              "</g>\n",
              "<!-- factor4 -->\n",
              "<g id=\"node10\" class=\"node\">\n",
              "<title>factor4</title>\n",
              "<ellipse fill=\"black\" stroke=\"black\" cx=\"123\" cy=\"-1.8\" rx=\"1.8\" ry=\"1.8\"/>\n",
              "</g>\n",
              "<!-- var7782220156096217089&#45;&#45;factor4 -->\n",
              "<g id=\"edge8\" class=\"edge\">\n",
              "<title>var7782220156096217089&#45;&#45;factor4</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M106.33,-40.17C112.51,-26.32 120.64,-8.1 122.57,-3.76\"/>\n",
              "</g>\n",
              "<!-- var7782220156096217090 -->\n",
              "<g id=\"node2\" class=\"node\">\n",
              "<title>var7782220156096217090</title>\n",
              "<ellipse fill=\"none\" stroke=\"black\" cx=\"315\" cy=\"-57.6\" rx=\"27\" ry=\"18\"/>\n",
              "<text text-anchor=\"middle\" x=\"315\" y=\"-53.9\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">l2</text>\n",
              "</g>\n",
              "<!-- factor5 -->\n",
              "<g id=\"node11\" class=\"node\">\n",
              "<title>factor5</title>\n",
              "<ellipse fill=\"black\" stroke=\"black\" cx=\"243\" cy=\"-1.8\" rx=\"1.8\" ry=\"1.8\"/>\n",
              "</g>\n",
              "<!-- var7782220156096217090&#45;&#45;factor5 -->\n",
              "<g id=\"edge10\" class=\"edge\">\n",
              "<title>var7782220156096217090&#45;&#45;factor5</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M297.57,-43.58C278.26,-29.15 249.13,-7.38 243.84,-3.43\"/>\n",
              "</g>\n",
              "<!-- factor6 -->\n",
              "<g id=\"node12\" class=\"node\">\n",
              "<title>factor6</title>\n",
              "<ellipse fill=\"black\" stroke=\"black\" cx=\"279\" cy=\"-1.8\" rx=\"1.8\" ry=\"1.8\"/>\n",
              "</g>\n",
              "<!-- var7782220156096217090&#45;&#45;factor6 -->\n",
              "<g id=\"edge12\" class=\"edge\">\n",
              "<title>var7782220156096217090&#45;&#45;factor6</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M304.39,-40.75C295.1,-26.86 282.63,-8.22 279.66,-3.78\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352320 -->\n",
              "<g id=\"node3\" class=\"node\">\n",
              "<title>var8646911284551352320</title>\n",
              "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-57.6\" rx=\"27\" ry=\"18\"/>\n",
              "<text text-anchor=\"middle\" x=\"27\" y=\"-53.9\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">x0</text>\n",
              "</g>\n",
              "<!-- factor0 -->\n",
              "<g id=\"node6\" class=\"node\">\n",
              "<title>factor0</title>\n",
              "<ellipse fill=\"black\" stroke=\"black\" cx=\"27\" cy=\"-1.8\" rx=\"1.8\" ry=\"1.8\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352320&#45;&#45;factor0 -->\n",
              "<g id=\"edge1\" class=\"edge\">\n",
              "<title>var8646911284551352320&#45;&#45;factor0</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M27,-39.58C27,-25.79 27,-7.97 27,-3.73\"/>\n",
              "</g>\n",
              "<!-- factor1 -->\n",
              "<g id=\"node7\" class=\"node\">\n",
              "<title>factor1</title>\n",
              "<ellipse fill=\"black\" stroke=\"black\" cx=\"100\" cy=\"-1.8\" rx=\"1.8\" ry=\"1.8\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352320&#45;&#45;factor1 -->\n",
              "<g id=\"edge2\" class=\"edge\">\n",
              "<title>var8646911284551352320&#45;&#45;factor1</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M44.67,-43.58C64.25,-29.15 93.78,-7.38 99.15,-3.43\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352320&#45;&#45;factor3 -->\n",
              "<g id=\"edge7\" class=\"edge\">\n",
              "<title>var8646911284551352320&#45;&#45;factor3</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M40.37,-41.61C52.8,-27.68 69.99,-8.41 74.09,-3.81\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352321 -->\n",
              "<g id=\"node4\" class=\"node\">\n",
              "<title>var8646911284551352321</title>\n",
              "<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-57.6\" rx=\"27\" ry=\"18\"/>\n",
              "<text text-anchor=\"middle\" x=\"171\" y=\"-53.9\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">x1</text>\n",
              "</g>\n",
              "<!-- var8646911284551352321&#45;&#45;factor1 -->\n",
              "<g id=\"edge3\" class=\"edge\">\n",
              "<title>var8646911284551352321&#45;&#45;factor1</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M153.81,-43.58C134.77,-29.15 106.05,-7.38 100.83,-3.43\"/>\n",
              "</g>\n",
              "<!-- factor2 -->\n",
              "<g id=\"node8\" class=\"node\">\n",
              "<title>factor2</title>\n",
              "<ellipse fill=\"black\" stroke=\"black\" cx=\"178\" cy=\"-1.8\" rx=\"1.8\" ry=\"1.8\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352321&#45;&#45;factor2 -->\n",
              "<g id=\"edge4\" class=\"edge\">\n",
              "<title>var8646911284551352321&#45;&#45;factor2</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M173.21,-39.58C175.01,-25.79 177.33,-7.97 177.88,-3.73\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352321&#45;&#45;factor4 -->\n",
              "<g id=\"edge9\" class=\"edge\">\n",
              "<title>var8646911284551352321&#45;&#45;factor4</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M157.63,-41.61C145.2,-27.68 128.01,-8.41 123.91,-3.81\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352321&#45;&#45;factor5 -->\n",
              "<g id=\"edge11\" class=\"edge\">\n",
              "<title>var8646911284551352321&#45;&#45;factor5</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M188.43,-43.58C207.74,-29.15 236.87,-7.38 242.16,-3.43\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352322 -->\n",
              "<g id=\"node5\" class=\"node\">\n",
              "<title>var8646911284551352322</title>\n",
              "<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-57.6\" rx=\"27\" ry=\"18\"/>\n",
              "<text text-anchor=\"middle\" x=\"243\" y=\"-53.9\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">x2</text>\n",
              "</g>\n",
              "<!-- var8646911284551352322&#45;&#45;factor2 -->\n",
              "<g id=\"edge5\" class=\"edge\">\n",
              "<title>var8646911284551352322&#45;&#45;factor2</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M226.6,-43.02C209.75,-29.08 185.18,-8.74 179.3,-3.87\"/>\n",
              "</g>\n",
              "<!-- var8646911284551352322&#45;&#45;factor6 -->\n",
              "<g id=\"edge13\" class=\"edge\">\n",
              "<title>var8646911284551352322&#45;&#45;factor6</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M253.61,-40.75C262.9,-26.86 275.37,-8.22 278.34,-3.78\"/>\n",
              "</g>\n",
              "</g>\n",
              "</svg>\n"
            ],
            "text/plain": [
              "<graphviz.sources.Source at 0x1d6fb24ec10>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Create a simple Gaussian Factor Graph\n",
        "graph = GaussianFactorGraph()\n",
        "model = gtsam.noiseModel.Isotropic.Sigma(1, 1.0)\n",
        "graph.add(X(0), -np.eye(1), np.zeros(1), model)           # Prior on x0\n",
        "graph.add(X(0), -np.eye(1), X(1), np.eye(1), np.zeros(1), model) # x0 -> x1\n",
        "graph.add(X(1), -np.eye(1), X(2), np.eye(1), np.zeros(1), model) # x1 -> x2\n",
        "graph.add(L(1), -np.eye(1), X(0), np.eye(1), np.zeros(1), model) # l1 -> x0 (measurement)\n",
        "graph.add(L(1), -np.eye(1), X(1), np.eye(1), np.zeros(1), model) # l1 -> x1 (measurement)\n",
        "graph.add(L(2), -np.eye(1), X(1), np.eye(1), np.zeros(1), model) # l2 -> x1 (measurement)\n",
        "graph.add(L(2), -np.eye(1), X(2), np.eye(1), np.zeros(1), model) # l2 -> x2 (measurement)\n",
        "\n",
        "print(\"Original Factor Graph:\")\n",
        "display(graphviz.Source(graph.dot()))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Resulting BayesTree:\n",
            ": cliques: 2, variables: 5\n",
            "- p(x1 l2 x2 )\n",
            "  R = [   1.61245 -0.620174 -0.620174 ]\n",
            "      [         0   1.27098  -1.08941 ]\n",
            "      [         0         0  0.654654 ]\n",
            "  d = [ 0 0 0 ]\n",
            "  mean: 3 elements\n",
            "  l2: 0\n",
            "  x1: 0\n",
            "  x2: 0\n",
            "  logNormalizationConstant: -2.46292\n",
            "  No noise model\n",
            "| - p(l1 x0  | x1)\n",
            "  R = [   1.41421 -0.707107 ]\n",
            "      [         0   1.58114 ]\n",
            "  S[x1] = [ -0.707107 ]\n",
            "          [ -0.948683 ]\n",
            "  d = [ 0 0 ]\n",
            "  logNormalizationConstant: -1.03316\n",
            "  No noise model\n",
            "\n",
            "Visualization:\n"
          ]
        },
        {
          "data": {
            "image/svg+xml": [
              "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
              "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
              " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
              "<!-- Generated by graphviz version 2.50.0 (0)\n",
              " -->\n",
              "<!-- Title: G Pages: 1 -->\n",
              "<svg width=\"103pt\" height=\"116pt\"\n",
              " viewBox=\"0.00 0.00 102.89 116.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
              "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n",
              "<title>G</title>\n",
              "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 98.89,-112 98.89,4 -4,4\"/>\n",
              "<!-- 2 -->\n",
              "<g id=\"node1\" class=\"node\">\n",
              "<title>2</title>\n",
              "<ellipse fill=\"none\" stroke=\"black\" cx=\"47.45\" cy=\"-90\" rx=\"44.69\" ry=\"18\"/>\n",
              "<text text-anchor=\"middle\" x=\"47.45\" y=\"-86.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">x1, l2, x2</text>\n",
              "</g>\n",
              "<!-- 3 -->\n",
              "<g id=\"node2\" class=\"node\">\n",
              "<title>3</title>\n",
              "<ellipse fill=\"none\" stroke=\"black\" cx=\"47.45\" cy=\"-18\" rx=\"47.39\" ry=\"18\"/>\n",
              "<text text-anchor=\"middle\" x=\"47.45\" y=\"-14.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">l1, x0 : x1</text>\n",
              "</g>\n",
              "<!-- 2&#45;&gt;3 -->\n",
              "<g id=\"edge1\" class=\"edge\">\n",
              "<title>2&#45;&gt;3</title>\n",
              "<path fill=\"none\" stroke=\"black\" d=\"M47.45,-71.7C47.45,-63.98 47.45,-54.71 47.45,-46.11\"/>\n",
              "<polygon fill=\"black\" stroke=\"black\" points=\"50.95,-46.1 47.45,-36.1 43.95,-46.1 50.95,-46.1\"/>\n",
              "</g>\n",
              "</g>\n",
              "</svg>\n"
            ],
            "text/plain": [
              "<graphviz.sources.Source at 0x1d6fb28be10>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Eliminate multifrontally using COLAMD ordering\n",
        "ordering = Ordering.ColamdGaussianFactorGraph(graph)\n",
        "# Note: Multifrontal typically yields multiple roots if graph is disconnected\n",
        "bayes_tree = graph.eliminateMultifrontal(ordering)\n",
        "\n",
        "print(\"Resulting BayesTree:\")\n",
        "bayes_tree.print()\n",
        "print(\"\\nVisualization:\")\n",
        "display(graphviz.Source(bayes_tree.dot()))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bayestree_access_code",
        "outputId": "56789abc-def0-1234-5678-9abcdef01234"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "BayesTree number of cliques: 2\n"
          ]
        }
      ],
      "source": [
        "print(f\"BayesTree number of cliques: {bayes_tree.size()}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bayestree_solve_md"
      },
      "source": [
        "## Solution and Marginals\n",
        "\n",
        "Similar to `BayesNet`, `BayesTree` (specifically derived types like `GaussianBayesTree`) provides an `optimize()` method for finding the MLE solution. It also allows for efficient computation of marginals on individual variables or joint marginals on pairs of variables using belief propagation or shortcut evaluation on the tree."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bayestree_solve_code",
        "outputId": "6789abcd-ef01-2345-6789-abcdef012345"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Optimized Solution (MLE):\n",
            "VectorValues: 5 elements\n",
            "  l1: 0\n",
            "  l2: 0\n",
            "  x0: 0\n",
            "  x1: 0\n",
            "  x2: 0\n",
            "\n",
            "Marginal Factor on x1:\n",
            "GaussianConditional p(x1)\n",
            "  R = [ 0.774597 ]\n",
            "  d = [ 0 ]\n",
            "  mean: 1 elements\n",
            "  x1: 0\n",
            "  logNormalizationConstant: -1.17435\n",
            "  No noise model\n",
            "\n",
            "Joint Marginal Factor Graph on (x0, x2):\n",
            "\n",
            "size: 2\n",
            "factor 0:  p(x0 | x2)\n",
            "  R = [ 1.32288 ]\n",
            "  S[x2] = [ -0.566947 ]\n",
            "  d = [ 0 ]\n",
            "  logNormalizationConstant: -0.639131\n",
            "  No noise model\n",
            "factor 1:  p(x2)\n",
            "  R = [ 0.654654 ]\n",
            "  d = [ 0 ]\n",
            "  mean: 1 elements\n",
            "  x2: 0\n",
            "  logNormalizationConstant: -1.34259\n",
            "  No noise model\n"
          ]
        }
      ],
      "source": [
        "# Optimize to find the MLE solution (for GaussianBayesTree)\n",
        "mle_solution = bayes_tree.optimize()\n",
        "print(\"Optimized Solution (MLE):\")\n",
        "mle_solution.print()\n",
        "\n",
        "# Compute marginal factor on a single variable (returns a Conditional)\n",
        "marginal_x1 = bayes_tree.marginalFactor(X(1))\n",
        "print(\"\\nMarginal Factor on x1:\")\n",
        "marginal_x1.print()\n",
        "\n",
        "# Compute joint marginal factor graph on two variables\n",
        "joint_x0_x2 = bayes_tree.joint(X(0), X(2))\n",
        "print(\"\\nJoint Marginal Factor Graph on (x0, x2):\")\n",
        "joint_x0_x2.print()"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "gtsam",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.13.1"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}